/**
 * Copyright 2012 Roger Cockshutt
 * krakenbrau@gmail.com
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.googlecode.parallelrl.process;

import java.util.List;
import java.util.Vector;

import com.googlecode.parallelrl.data.ecs.ECSManager;
import com.googlecode.parallelrl.util.ComponentPair;
import com.googlecode.parallelrl.util.Point2D;

/**
 * @author Roger Cockshutt
 *
 */
public abstract class ECSTools {
	
	/**
	 * Returns true if pos2 is within distance of pos1.  Otherwise, returns false.
	 * 
	 * @param pos1
	 * @param pos2
	 * @param distance
	 * @return
	 */
	public static boolean isWithin(Point2D pos1, Point2D pos2, Double distance) {
		if (pos1 == null || pos2 == null || distance == null) {
			return false;
		}
		return (Math.abs(pos1.distanceTo(pos2)) <= distance);
	}
	
	/**
	 * Return a list of entities from the given list that have the specified component.
	 * 
	 * @param entities
	 * @param component
	 * @return
	 */
	public static List<Long> selectForComponent(List<Long> entities, String component) {
		List<Long> selection = new Vector<>();
		if (entities != null && !entities.isEmpty()) {
			for (Long id : entities) {
				if (ECSManager.hasComponent(id, component)) {
					selection.add(id);
				}
			}
		}
		return selection;
	}
	
	public static Double applyCurve(Double in, Double a, Double b) {
		Double x = Math.abs(in);
		if (x != 0) {
			Double y = a * Math.log((x / b) + 1D);		// y = +/- (a*ln(|x|/b + 1))
			if (in < 0) {
				y = 0 - y;
			}
			return y;
		}
		return 0D;
	}
	
	/**
	 * Return a list of entities from the given list that do not have the specified component.
	 * 
	 * @param entities
	 * @param component
	 * @return
	 */
	public static List<Long> selectAgainstComponent(List<Long> entities, String component) {
		List<Long> selection = new Vector<>();
		for (Long id : entities) {
			if (!ECSManager.hasComponent(id, component)) {
				selection.add(id);
			}
		}
		return selection;
	}
	
	/**
	 * Return a list of entities from the given list that have the specified component with a value other than the specified value.
	 * 
	 * @param entities
	 * @param component
	 * @param value
	 * @param exact  If true, entities will be selected if they have the specified component and its value is not equal to the 
	 * specified value.  If false, entities will be selected if they have the specified component and its value does not contain
	 * the specified value.
	 * @return
	 */
	public static List<Long> selectAgainstComponentValue(List<Long> entities, String component, String value, boolean exact) {
		List<Long> selection = new Vector<>();
		for (Long id : entities) {
			ComponentPair cmp = ECSManager.getComponent(id, component);
			if (cmp != null) {
				if (exact) {
					if (!cmp.value().equals(value)) {
						selection.add(id);
					}
				} else {
					if (!cmp.value().contains(value)) {
						selection.add(id);
					}
				}
			}
		}
		return selection;
	}
	
	/**
	 * Return a list of entities from the given list that have the specified component with the specified value.
	 * 
	 * @param entities
	 * @param component
	 * @param value
	 * @param exact If true, entities will be selected if they have the specified component and its value is
	 * equal to the specified value.  If false, entities will be selected if they have the specified component and its
	 * value contains the specified value.
	 * @return
	 */
	public static List<Long> selectForComponentValue(List<Long> entities, String component, String value, boolean exact) {
		List<Long> selection = new Vector<>();
		for (Long id : entities) {
			ComponentPair cmp = ECSManager.getComponent(id, component);
			if (cmp != null) {
				if (exact) {
					if (cmp.value().equals(value)) {
						selection.add(id);
					}
				} else {
					if (cmp.value().contains(value)) {
						selection.add(id);
					}
				}
			}
		}
		return selection;
	}
	
	/**
	 * Return the ID of the nearest entity within a distance maxDistance of the
	 * given entity.
	 * 
	 * @param thisEntity
	 * @param maxDistance
	 * @param exclusive If true, does not consider entities with distance 0 from the given position.
	 * @return
	 */
	public static Long getNearestWithin(Long thisEntity, Integer maxDistance, boolean exclusive) {
		ComponentPair posCmp = ECSManager.getComponent(thisEntity, "Position");
		if (posCmp == null) {
			return null;
		}
		Point2D thisPos = posCmp.coordinateValue();
		return getNearestTo(thisPos, getAllWithin(thisPos, maxDistance, exclusive), exclusive);
	}
	
	/**
	 * Return a list of entities within a distance maxDistance of the specified position.
	 * 
	 * @param pos
	 * @param maxDistance
	 * @param exclusive If true, does not consider entities with distance 0 from the given position.
	 * @return
	 */
	public static List<Long> getAllWithin(Point2D pos, Integer maxDistance, boolean exclusive) {
		List<Long> IDs = new Vector<>();
		List<Long> relevantEntities = ECSManager.getAllEntitiesPossessingComponent("Position");
		for (Long eID : relevantEntities) {
			Point2D ePos = ECSManager.getComponent(eID, "Position").coordinateValue();
			Double distance = Math.abs(pos.distanceTo(ePos));
			if (distance.intValue() <= maxDistance) {
				if ((exclusive && distance != 0) || !exclusive) {
					IDs.add(eID);
				}
			}
		}
		return IDs;
	}
	
	/**
	 * Return the ID of the nearest entity to the specified position.
	 * 
	 * @param pos
	 * @param relevantEntities
	 * @param exclusive If true, does not consider entities with distance 0 from the given position.
	 * @return
	 */
	public static Long getNearestTo(Point2D pos, List<Long> relevantEntities, boolean exclusive) {
		Double closestDist = null;
		Long closestID = null;
		for (Long eID : relevantEntities) {
			if (!ECSManager.hasComponent(eID, "Position")) {
				continue;
			}
			Point2D ePos = ECSManager.getComponent(eID, "Position").coordinateValue();
			Double distance = Math.abs(pos.distanceTo(ePos));
			if (closestDist == null
					|| distance <= closestDist) {
				if ((exclusive && distance != 0) || !exclusive) {
					closestDist = distance;
					closestID = eID;
				}
			}
		}
		return closestID;
	}

}
